﻿////////////////////////////////////////////////////////////////////////////////////////
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING THIS SOFTWARE
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install, copy or use the software.
//
// Copyright (C) 2013, AerVision Technologies Pty Ltd, all rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of AerVision Technologies, may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors to demonstrate 
// www.AerCloud.net Framework as a Service API (FaaS), "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the copyright holder or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace App_AerCloudDemo.Utils
{
    public class ImageData
    {
        byte[] image_data_png = null;
        byte[] image_data_jpg = null;
        BitmapSource bitmap = null;
        string ext = "";

        public ImageData(string fileName)
        {
            ext = Path.GetExtension(fileName).ToLower();

            string image_format = ext.Substring(1);

            if (ext == ".png")
                image_data_png = File.ReadAllBytes(fileName);
            else if (ext == ".jpg" || ext == ".jpeg")
                image_data_jpg = File.ReadAllBytes(fileName);
            else
            {
                MessageLogger.Default.WriteLineWithTime("Error> Invalid file extension: " + fileName);
                return;
            }

            Dispatcher.CurrentDispatcher.Invoke(new Action(() =>
            {
                BitmapSource temp = null;
                if (ext == ".png")
                    temp = ImageUtils.DecodePng(image_data_png);
                else if (ext == ".jpg" || ext == ".jpeg")
                    temp = ImageUtils.DecodeJpg(image_data_jpg);
                
                bitmap = temp;
            }));
        }

        public ImageData(byte[] data_png)
        {
            image_data_png = data_png;

            Dispatcher.CurrentDispatcher.Invoke(new Action(() =>
            {
                var temp = ImageUtils.DecodePng(image_data_png);
                bitmap = temp;
            }));
        }

        public ImageData(BitmapSource bitmap)
        {
            this.bitmap = bitmap;

            if (image_data_png == null)
                image_data_png = ImageUtils.EncodeBitmapPng(bitmap);
        }

        public byte[] asPng
        { 
            get 
            {
                if (image_data_png == null)
                    image_data_png = ImageUtils.EncodeBitmapPng(bitmap);

                return image_data_png;
            }
        }

        public BitmapSource asBitmap
        {
            get 
            {
                while (bitmap == null)
                    System.Threading.Thread.Sleep(1);
                return bitmap;
            }
        }
    }

    internal class AerCloudNet_Interface
    {
        private static ISMWebAPIServiceReference.ISMWebAPIClient AerCloud_net = null;

        public static bool IsAccessKeyValid { get; private set; }

        public static bool IsConnected()
        {
            if (!string.IsNullOrWhiteSpace(Settings.Default.AccessKey))
                if (AerCloud_net != null)
                    if (AerCloud_net.State == CommunicationState.Opened)
                        if (IsAccessKeyValid)
                            return true;

            return false;                    
        }

        public static void Connect()
        {
            int error_count = 0;
            while (true)
            {
                try
                {
                    AerCloud_net = new ISMWebAPIServiceReference.ISMWebAPIClient("BasicHttpBinding_IISMWebAPI");

                    IsAccessKeyValid = false;
                    AerCloud_net.Open();
                    while (AerCloud_net.State == CommunicationState.Opening)
                        System.Threading.Thread.Sleep(500);

                    if (AerCloud_net.State == CommunicationState.Opened)
                    {
                        var s = AerCloud_net.query(null, null);
                        break;
                    }
                }
                catch (Exception ex)
                {
                    error_count++;
                    if (error_count > 3)
                    {
                        MessageLogger.Default.WriteLineWithTime("ERROR: " + ex + "\n");
                        throw;
                    }
                }
                System.Threading.Thread.Sleep(1000);
            }
        }

        public static void InitConnection(MainWindow mainWindow)
        {
            if (string.IsNullOrWhiteSpace(Settings.Default.AccessKey))
                Settings.Default.AccessKey = "";

            MessageLogger.Default.WriteLineWithTime("Connecting to the remote server...");
            Task.Factory.StartNew(() => 
                {
                    try
                    {
                        Connect();
                        MessageLogger.Default.WriteLineWithTime("Connection to remote server is OK.");

                        if (string.IsNullOrWhiteSpace(Settings.Default.AccessKey))
                        {
                            MessageLogger.Default.WriteLineWithTime("Access key is required");
                            return;
                        }

                        MessageLogger.Default.WriteLineWithTime("Sending a test query using provided access key. Text is 'HELLO'...");
                        try
                        {
                            var s = AerCloud_net.query(Settings.Default.AccessKey, "HELLO");
                            MessageLogger.Default.WriteLineWithTime("Received reply '" + s);
                            MessageLogger.Default.WriteLineWithTime("Excellent! everything works fine.");
                            IsAccessKeyValid = true;
                            mainWindow.UpdateControls();
                        }
                        catch (FaultException<ISMWebAPIServiceReference.fault_exception> ex)
                        {
                            MessageLogger.Default.WriteLineWithTime("Server Error> Received for provided access key: " + ex.Detail.message);
                        }


                    }
                    catch (Exception ex)
                    {
                        MessageLogger.Default.WriteLineWithTime("Connecting failed. " + ex.Message);
                    }
                }
            );
        }

        public static ImageData GetImage(string personName, string imageName)
        {
            try
            {
                var data = ImageCache.Get(personName, imageName);
                if (data == null)
                {
                    MessageLogger.Default.WriteLineWithTime("AerCloud.net call> [" + personName + " - " + imageName + "] Retrieving face image...");
                    var face_image = AerCloud_net.get_image_png(Settings.Default.AccessKey, personName, imageName);

                    data = new ImageData(face_image);
                    ImageCache.Put(personName, imageName, data);
                }
                return data;
            }
            catch (FaultException<ISMWebAPIServiceReference.fault_exception> ex)
            {
                MessageLogger.Default.WriteLineWithTime("AerCloud.net ERROR> [" + imageName + "] " + ex.Message);
            }
            catch (Exception ex)
            {
                MessageLogger.Default.WriteLineWithTime("ERROR> [" + imageName + "] " + ex.Message);
            }
            return null;
        }

        public static void SaveImage(string personName, string imageName, ImageData data)
        {
            try
            {
                MessageLogger.Default.WriteLineWithTime("AerCloud.net call> [" + personName + " - " + imageName + "] Saving face");
                if (AerCloud_net.save_image(Settings.Default.AccessKey, "face", "png", data.asPng, personName, imageName, true))
                    MessageLogger.Default.WriteLineWithTime("[" + personName + " - " + imageName + "] " + "Save successful");
                else
                    MessageLogger.Default.WriteLineWithTime("[" + personName + " - " + imageName + "] + " + "Save failed");

                ImageCache.Delete(personName, imageName);
                NameCache.AddImageName(personName, imageName);
            }
            catch (FaultException<ISMWebAPIServiceReference.fault_exception> ex)
            {
                MessageLogger.Default.WriteLineWithTime("AerCloud.net ERROR> [" + imageName + "] " + ex.Message);
            }
            catch (Exception ex)
            {
                MessageLogger.Default.WriteLineWithTime("ERROR> [" + imageName + "] " + ex.Message);
            }
        }

        public static List<string> GetImageNames(string personName)
        {
            try
            {
                var result = NameCache.GetImageNames(personName);
                if (result == null)
                {
                    MessageLogger.Default.WriteLineWithTime("AerCloud.net call> Retrieving list of saved images from server...");
                    result = AerCloud_net.get_image_names(Settings.Default.AccessKey, personName);
                    NameCache.AddImageNames(personName, result);
                }
                return result;
            }
            catch (FaultException<ISMWebAPIServiceReference.fault_exception> ex)
            {
                MessageLogger.Default.WriteLineWithTime("AerCloud.net ERROR> " + ex.Message);
            }
            catch (Exception ex)
            {
                MessageLogger.Default.WriteLineWithTime("ERROR> " + ex.Message);
            }
            return null;
        }

        public static List<string> GetNames()
        {
            MessageLogger.Default.WriteLineWithTime("AerCloud.net call> Retrieving list of names from server...");
            return AerCloud_net.get_names(Settings.Default.AccessKey);
        }

        public static ImageData DetectFace(string imageName, ImageData imageData)
        {
            MessageLogger.Default.WriteLineWithTime("AerCloud.net call> Detecting faces " + "[" + imageName + "]...");
            var faces = AerCloud_net.detect(Settings.Default.AccessKey, "face", "png", imageData.asPng);

            if (faces.Count() >= 1)
            {
                if (faces.Count() > 1)
                    MessageLogger.Default.WriteLineWithTime("[" + imageName + "] " + "There is more than one face present in this image, the biggest face is saved");
                else
                    MessageLogger.Default.WriteLineWithTime("[" + imageName + "] " + "One face detected");

                var face = faces[0];
                for (var i = 1; i < faces.Count(); i++)
                    if (face.width < faces[i].width)
                        face = faces[i];

                return new ImageData(ImageUtils.CropRect(imageData.asBitmap, new Rect(face.x, face.y, face.width, face.height), PixelFormats.Bgra32, new Size(face.width, face.height)));
            }
            else
                MessageLogger.Default.WriteLineWithTime("[" + imageName + "] " + "There is no face present in this image");

            return null;
        }

        public static List<ImageData> DetectFaces(string imageName, ImageData imageData)
        {
            try
            {
                MessageLogger.Default.WriteLineWithTime("AerCloud.net call> Detecting faces " + "[" + imageName + "]...");
                var faces = AerCloud_net.detect(Settings.Default.AccessKey, "face", "png", imageData.asPng);

                if (faces.Count() >= 1)
                {
                    if (faces.Count() > 1)
                        MessageLogger.Default.WriteLineWithTime("[" + imageName + "] " + faces.Count().ToString() + " faces detected");
                    else
                        MessageLogger.Default.WriteLineWithTime("[" + imageName + "] " + "One face detected");

                    var result = new List<ImageData>();
                    foreach (var face in faces)
                        result.Add( new ImageData(ImageUtils.Resize(ImageUtils.CropFace(imageData.asBitmap, new Rect(face.x, face.y, face.width, face.height)), new Size(128, 128))));

                    return result;
                }
                else
                    MessageLogger.Default.WriteLineWithTime("[" + imageName + "] " + "There is no face present in this image");
            }
            catch (FaultException<ISMWebAPIServiceReference.fault_exception> ex)
            {
                MessageLogger.Default.WriteLineWithTime("AerCloud.net ERROR> [" + imageName + "] " + ex.Message);
            }
            catch (Exception ex)
            {
                MessageLogger.Default.WriteLineWithTime("ERROR> [" + imageName + "] " + ex.Message);
            }
            return null;
        }


        public static List<SearchResultItemView> SearchFace(string imageName, BitmapSource image)
        {
            try
            {
                MessageLogger.Default.WriteLineWithTime("AerCloud.net call> Search face " + "[" + imageName + "]");
                var results = AerCloud_net.search_by_image(Settings.Default.AccessKey, "face", "png", ImageUtils.EncodeBitmapPng(image), 10000, true); // the last parameter must be true
                MessageLogger.Default.WriteLineWithTime("[" + imageName + "] Search completed");

                var returnValue = new List<SearchResultItemView>();
                foreach (var item in results)
                    returnValue.Add(new SearchResultItemView(item, image));

                return returnValue;
            }
            catch (FaultException<ISMWebAPIServiceReference.fault_exception> ex)
            {
                MessageLogger.Default.WriteLineWithTime("AerCloud.net Error> [" + imageName + "]: " + ex.Detail.message);

            }
            catch (Exception ex)
            {
                MessageLogger.Default.WriteLineWithTime("Error> " + ex.ToString());
            }

            return null;
        }

        public static void DeleteName(string personName)
        {
            MessageLogger.Default.WriteLineWithTime("AerCloud.net call> Delete name " + "[" + personName + "]");
            var results = AerCloud_net.delete_images(Settings.Default.AccessKey, personName);
            ImageCache.Delete(personName);
        }
    }
}
